#pragma once

#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

class CCamera
{
private:
	D3DXVECTOR3		m_vPosition;
	D3DXVECTOR3		m_vCamOffset;

	D3DXQUATERNION	m_qOrientation;
	D3DXMATRIX		m_mView, m_mProjection;

	float			m_fRPM;
	float			m_fSpeed;

	float			m_fYawAngle;

public:
	CCamera()
	{
		D3DXQuaternionIdentity( & m_qOrientation );
		D3DXMatrixIdentity( & m_mProjection );
		D3DXMatrixIdentity( & m_mView );

		m_fRPM = 60.0f;
		m_fSpeed = 100.0f;
		m_vPosition = D3DXVECTOR3(0.0f, 0.0f, 0.0f);

		D3DXMatrixPerspectiveFovLH( &m_mProjection, D3DX_PI/4, 4.0f/3.0f, 1.0f, 10000.0f );
	}

	D3DXMATRIX * getViewMatrix() 
	{
		return &m_mView;
	}

	D3DXMATRIX * getProjectionMatrix() 
	{
		return &m_mProjection;
	}

	void Update()
	{
		// 1) Build a new view matrix

		// 1.1) First calcuate Translation
		D3DXMATRIX mTranslation;

		D3DXMatrixTranslation(  &mTranslation , 
			-m_vPosition.x , 
			-m_vPosition.y , 
			-m_vPosition.z );

		// 1.2) Now calculate rotation, by taking the conjucate of the quaternion
		D3DXMATRIX mRotation;
		D3DXQUATERNION mQ( -m_qOrientation.x , 
			               -m_qOrientation.y , 
			               -m_qOrientation.z ,
			                m_qOrientation.w );
		D3DXMatrixRotationQuaternion( &mRotation, 
			&mQ);

		// 2) Apply rotation & translation matrix at view matrix
		D3DXMatrixMultiply(&m_mView , &mTranslation , &mRotation );
	}

	D3DXMATRIX GetOrientation() const
	{
		D3DXMATRIX mRotation;
		D3DXQUATERNION mQ( -m_qOrientation.x, 
			               -m_qOrientation.y, 
			               -m_qOrientation.z,
			                m_qOrientation.w );

		D3DXMatrixRotationQuaternion( &mRotation, &mQ );
		return mRotation;
	}

	D3DXMATRIX GetOrientationXZ() const
	{
		D3DXMATRIX mRotation;
		double heading;
		double attitude;
		double bank;

		D3DXQUATERNION q1( -m_qOrientation.x, 
			               -m_qOrientation.y, 
			               -m_qOrientation.z,
			                m_qOrientation.w );

		double test = q1.x*q1.y + q1.z*q1.w;
		if (test > 0.499) { // singularity at north pole
			heading = 2 * atan2(q1.x,q1.w);
			attitude = D3DX_PI/2;
			bank = 0;
		}
		else if (test < -0.499) { // singularity at south pole
			heading = -2 * atan2(q1.x,q1.w);
			attitude = - D3DX_PI/2;
			bank = 0;
		}
		else
		{
		double sqx = q1.x*q1.x;
		double sqy = q1.y*q1.y;
		double sqz = q1.z*q1.z;
		heading = atan2(2.0*q1.y*q1.w-2.0*q1.x*q1.z , 1.0 - 2*sqy - 2*sqz);
		attitude = asin(2*test);
		bank = atan2(2.0*q1.x*q1.w-2.0*q1.y*q1.z , 1.0 - 2*sqx - 2*sqz);
		}

		D3DXMatrixRotationYawPitchRoll(
		  &mRotation,
		  heading,
		  0/*bank*/,
		  attitude);

		return mRotation;
	}

	D3DXVECTOR3 GetPosition() const
	{
		return D3DXVECTOR3( -m_vPosition.x, -m_vPosition.y, -m_vPosition.z );
	}

	void setLookAt(D3DXVECTOR3 vFrom, D3DXVECTOR3 vTo, D3DXVECTOR3 vUp)
	{
		// Setup rotation matrix
		D3DXMatrixLookAtLH(&m_mView, &vFrom , &vTo , &vUp);

		m_vPosition = vFrom;

		// Get the orientation
		D3DXQuaternionRotationMatrix(&m_qOrientation , &m_mView);
	}

	void setPos(D3DXVECTOR3 vNewPos)
	{
		m_vPosition = vNewPos;
	}

	D3DXVECTOR3 getPos()
	{
		return m_vPosition;
	}

	void applyPitch( float fAngle )
	{
       fAngle *= (m_fRPM / 60); // angle * per minute rotation
	   RotateXAxis( & m_qOrientation , fAngle );
	   D3DXQuaternionNormalize( & m_qOrientation , & m_qOrientation );
	}

	void UpdateCamera(float timeDelta)
	{
		if( KEY_DOWN('Z') )		Move( 1.0f * timeDelta);
		if( KEY_DOWN('S') )		Move( -1.0f * timeDelta);
		if( KEY_DOWN('Q') )		Strafe(-1.0f * timeDelta);
		if( KEY_DOWN('D') )		Strafe(1.0f * timeDelta);
		if( KEY_DOWN('R') )		Up(1.0f * timeDelta);
		if( KEY_DOWN('F') )		Up(-1.0f * timeDelta);

		if( KEY_DOWN(VK_UP))	Pitch(0.02f);
		if( KEY_DOWN(VK_DOWN))	Pitch(-0.02f);
		if( KEY_DOWN(VK_LEFT))	Yaw(-0.02f);
		if( KEY_DOWN(VK_RIGHT))	Yaw(0.02f);
		if( KEY_DOWN('N') )		Roll(0.02f);
		if( KEY_DOWN('M') )		Roll(-0.02f);
	}

	void Pitch( float fAngle )
	{
       fAngle *= (m_fRPM / 60); // angle * per minute rotation
	   RotateXAxis(&m_qOrientation , fAngle);
	   D3DXQuaternionNormalize( & m_qOrientation , & m_qOrientation );
	}

	void Roll( float fAngle )
	{
       fAngle *= (m_fRPM / 60); // angle * per minute rotation
	   RotateZAxis( & m_qOrientation , fAngle );
	   D3DXQuaternionNormalize( & m_qOrientation , & m_qOrientation );
	}

	void Yaw( float fAngle )
	{
       fAngle *= (m_fRPM / 60); // angle * per minute rotation
	   RotateYAxis( & m_qOrientation , fAngle );
	   D3DXQuaternionNormalize( & m_qOrientation , & m_qOrientation );
	}

    void Move( float fDistance )
	{
		D3DXVECTOR3 vDir = getAxisZ();
		m_vPosition += vDir * fDistance * m_fSpeed;
	}

    void Strafe( float fDistance )
	{
		D3DXVECTOR3 vDir = getAxisX();
		m_vPosition += vDir * fDistance * m_fSpeed;
	}

    void Up( float fDistance )
	{
		D3DXVECTOR3 vDir = getAxisY();
		m_vPosition += vDir * fDistance * m_fSpeed;
	}

	const D3DXVECTOR3 getAxisX() const
	{	
		D3DXVECTOR3 vAxis;

		vAxis.x = m_mView._11;
		vAxis.y = m_mView._21;
		vAxis.z = m_mView._31;

		return vAxis;
	}

	const D3DXVECTOR3 getAxisY() const
	{
		D3DXVECTOR3 vAxis;

		vAxis.x = m_mView._12;
		vAxis.y = m_mView._22;
		vAxis.z = m_mView._32;

		return vAxis;
	}

	const D3DXVECTOR3 getAxisZ() const
	{
		D3DXVECTOR3 vAxis;

		vAxis.x = m_mView._13;
		vAxis.y = m_mView._23;
		vAxis.z = m_mView._33;

		return vAxis;
	}

	bool RotateAxis(D3DXQUATERNION *pOrientation, D3DXVECTOR3 *pAxis, float fAngle)
	{ 
		bool Success=false;

		if(pOrientation && pAxis)
		{
			D3DXQUATERNION Rotation;

			D3DXQuaternionRotationAxis(	&Rotation, 
				TransformVector(pOrientation, pAxis), 
				fAngle);
			*pOrientation *= Rotation; 

			Success = true;
		}

		return(Success);
	}


	bool RotateXAxis(D3DXQUATERNION *pOrientation, float fAngle)
	{ 
		bool bSuccess = false;

		if(pOrientation)
		{
			D3DXQUATERNION Rotation;
            D3DXVECTOR3 A(1.0f, 0.0f, 0.0f);
			D3DXQuaternionRotationAxis(	&Rotation, 
				TransformVector(pOrientation, 
				&A), 
				fAngle);
			*pOrientation *= Rotation;

			bSuccess = true;
		}

		return(bSuccess);
	}


	bool RotateYAxis(D3DXQUATERNION *pOrientation, float fAngle)
	{ 
		bool bSuccess = false;

		if(pOrientation)
		{
			D3DXQUATERNION Rotation;
D3DXVECTOR3 B(0.0f, 1.0f, 0.0f);
			D3DXQuaternionRotationAxis(	&Rotation, 
				TransformVector(pOrientation, 
				&B), 
				fAngle);
			*pOrientation *= Rotation;

			bSuccess = true;
		}

		return(bSuccess);
	}


	bool RotateZAxis(D3DXQUATERNION *pOrientation, float fAngle)
	{ 
		bool bSuccess = false;

		if(pOrientation)
		{
			D3DXQUATERNION Rotation;
D3DXVECTOR3 C(0.0f, 0.0f, 1.0f);
			D3DXQuaternionRotationAxis(	&Rotation, 
				TransformVector( pOrientation, 
				&C), 
				fAngle);
			*pOrientation *= Rotation;

			bSuccess = true;
		}

		return(bSuccess);
	}

		D3DXVECTOR3* TransformVector(D3DXQUATERNION *pOrientation, D3DXVECTOR3 *pAxis)
	{
		D3DVECTOR vNewAxis;
		D3DXMATRIX matRotation;

		// Build a matrix from the quaternion.
		D3DXMatrixRotationQuaternion(&matRotation, pOrientation); 

		// Transform the queried axis vector by the matrix.
		vNewAxis.x = pAxis->x * matRotation._11 + pAxis->y * matRotation._21 + pAxis->z * matRotation._31 + matRotation._41; 
		vNewAxis.y = pAxis->x * matRotation._12 + pAxis->y * matRotation._22 + pAxis->z * matRotation._32 + matRotation._42;
		vNewAxis.z = pAxis->x * matRotation._13 + pAxis->y * matRotation._23 + pAxis->z * matRotation._33 + matRotation._43;

		memcpy(pAxis, &vNewAxis, sizeof(vNewAxis)); // Copy axis.

		return(pAxis);
	}
};